--==========================================
-- 文件上传: 
--[[ 
    curl http://192.168.1.205/upload \
        -F 'data=@bin/bashrc/bash.sh' \
        -H 'hostname:test.exp.net' \
        -H 'filemd5:cab8acb6d32e3b01be9e19efec57fc33'
--]]
--==========================================
local upload = require("resty.upload")
-- local cjson = require("cjson")
local mstring = require("common.libstring")
local mlfs = require("common.liblfs")
local resty_md5 = require("resty.md5")
local str = require("resty.string")

local chunk_size = 4096  -- should be set to 4096 or 8192
                         -- for real-world settings

local form, err = upload:new(chunk_size)
if not form then
    ngx.log(ngx.ERR, "failed to new upload: ", err)
    ngx.exit(ngx.HTTP_INTERNAL_SERVER_ERROR)
end

form:set_timeout(1000)   -- 1 sec

-- 文件保存的根路径
local save_root_path = fb_root_path or "/usr/local/openresty/nginx/upload/"

-- 保存的文件对象
local file_to_save

--文件是否成功保存
local ret_save = false

-- 检查路径
local function checkSaveDir(p)
    if not mlfs.isDir(p) then
        if not mlfs.createDir(p) then
            ngx.log(ngx.ERR, "failed to create new path : ", p)
            return false
        end
    end
    return true
end

-- 检查本地文件MD5
local function checkFileMD5(f)
    local local_file_md5 = ''
    local local_file_md5_path = f .. '.md5'
    if mlfs.exFile(f) then
        if mlfs.exFile(local_file_md5_path) then
            local read_f = io.input(local_file_md5_path)
            while true do
                line = io.read()      --逐行读取内容，文件结束时返回nil
                if nil == line then
                    break
                elseif nil ~= string.find(line,"MD5:") then
                    local_file_md5 = string.gsub(line, 'MD5:', '')
                    break
                end
            end
            read_f:close()
        else
            local read_f = io.open(f,"rb")
            local_file_md5 = ngx.md5(read_f:read("*a"))
            read_f:close()
        end
    end
    return local_file_md5
end

-- 获取请求头,通过请求头生成文件保存路径
local save_file_md5
local save_file_root_path
local save_file_type
local headers_tab = ngx.req.get_headers()
for k, v in pairs(headers_tab) do
    if k == 'hostname' then
        save_file_root_path = v
    elseif k == 'filetype' then
        save_file_type = v
    elseif k == 'filemd5' then
        save_file_md5 = v
    end
end

-- 生成文件存储路径
local save_file_path
if save_file_root_path then
    if save_file_type then
        save_file_path = string.gsub(save_root_path .. '/' .. save_file_root_path .. '/' .. save_file_type .. '/', "//", "/")
    else
        save_file_path = string.gsub(save_root_path .. '/' .. save_file_root_path .. '/', "//", "/")
    end
else
    save_file_path = string.gsub(save_root_path .. '/', "//", "/")
end
if not checkSaveDir(save_file_path) then
    ngx.say("failed to save file ", upload_filename)
    ngx.log(ngx.ERR, "failed to create dir ", save_file_path)
    return
end

local upload_file_md5 = resty_md5:new()
local upload_filename
while true do
    local typ, res, err = form:read()
    if not typ then
        ngx.say("failed to read: ", err)
        ngx.log(ngx.ERR, "failed to read: ", err)
        return
    end
    
    if typ == "header" then
        -- 开始读取 http header
        -- 解析出本次上传的文件名
        local key = res[1]
        local value = res[2]
        ngx.log(ngx.ERR, "key: ", key , "; value: ", value)
        if key == "Content-Disposition" then
            -- 解析出本次上传的文件名
            -- form-data; name="testFileName"; filename="testfile.txt"
            local kvlist = mstring.split(value, ';')
            for _, kv in ipairs(kvlist) do
                local seg = mstring.trim(kv)
                if seg:find("filename") then
                    local kvfile = mstring.split(seg, "=")
                    upload_filename = string.sub(kvfile[2], 2, -2)
                    if upload_filename then
                        local local_file_md5 = checkFileMD5(save_file_path .. upload_filename)
                        if save_file_md5 == local_file_md5 then
                            ngx.say("File already exists: ", upload_filename)
                            ngx.log(ngx.INFO, "File already exists: ", upload_filename)
                            return
                        end
                        file_to_save = io.open(save_file_path .. upload_filename .. ".tmp", "w+")
                        if not file_to_save then
                            ngx.say("failed to open file ", upload_filename)
                            ngx.log(ngx.ERR, "failed to open file ", upload_filename)
                            return
                        end
                        ngx.log(ngx.INFO, "Now upload : ", save_file_path .. upload_filename .. ".tmp")
                        break
                    end
                end
            end
        end
    elseif typ == "body" then
        -- 开始读取 http body
        if file_to_save then
            file_to_save:write(res)
            upload_file_md5:update(res)
        end
    elseif typ == "part_end" then
        -- 文件写结束，关闭文件
        if file_to_save then
            file_to_save:close()
            file_to_save = nil
            local digest = upload_file_md5:final()
            local upload_file_md5_sum = str.to_hex(digest)
            if upload_file_md5_sum ~= save_file_md5 then
                ngx.say("Upload file failed.")
                ngx.log(ngx.ERR, "Upload file failed: ", upload_filename)
                return
            end
        end
        
        ret_save = true
    elseif typ == "eof" then
        -- 文件读取结束
        break
    else
        ngx.log(ngx.INFO, "do other things")
    end
end

if ret_save then
    if mlfs.exFile(save_file_path .. upload_filename) then
        os.remove(save_file_path .. upload_filename)
    end
    os.rename(save_file_path .. upload_filename .. ".tmp", save_file_path .. upload_filename)
    local local_md5_file = io.open(save_file_path .. upload_filename .. ".md5","w")
    local_md5_file:write("MD5:" .. save_file_md5)
    local_md5_file:flush()
    local_md5_file:close()
    ngx.log(ngx.INFO, "save file ok")
    ngx.say("save file ok")
end